package com.mikelouis.test.scala.impatient.chapter18_advanced_types

/**
 * Created by yuazhu on 5/4/2015.
 */
object Chapter18 extends App {

  // 1
  {
    class Bug {
      private var pos = 0
      private var direc = 1

      def show(): this.type = {
        println(pos)
        this
      }

      def move(steps: Int): this.type = {
        pos += steps * direc
        this
      }

      def turn(): this.type = {
        direc *= -1
        this
      }
    }

    val bugsy: Bug = new Bug
    bugsy.move(4).show().move(6).show().turn().move(5).show()
  }

  // 2
  {
    object Show
    object Then
    object Around

    class Bug {
      private var pos = 0
      private var direc = 1

      def show(): this.type = {
        println(pos)
        this
      }

      def move(steps: Int): this.type = {
        pos += steps * direc
        this
      }

      def turn(): this.type = {
        direc *= -1
        this
      }

      def and(showLah: Show.type): this.type = {
        show()
        this
      }

      def and(thenLah: Then.type): this.type = this

      def turn(aroundLah: Around.type): this.type = turn()
    }

    val bugsy: Bug = new Bug
    bugsy move 4 and Show and Then move 6 and Show turn Around move 5 and Show
  }

  // 3
  {
    class DocumentProperty
    object Title extends DocumentProperty
    object Author extends DocumentProperty

    class Document(var title: String, var author: String) {
      def setTitle(title: String): this.type = {
        this.title = title
        this
      }

      def setAuthor(author: String): this.type = {
        this.author = author
        this
      }

      override def toString: String = "title: " + title + ", author: " + author
    }

    class Book(title: String = "", author: String = "", var chapter: String = "") extends Document(title, author) {
      def addChapter(chapter: String) = {
        this.chapter = chapter
        this
      }

      private var useNextArgAs: Any = null

      def set(obj: DocumentProperty): this.type = {
        useNextArgAs = obj
        this
      }

      def to(arg: String): this.type = useNextArgAs match {
        case next: Title.type => setTitle(arg); this
        case next: Author.type => setAuthor(arg); this
        case _ => this
      }

      override def toString: String = super.toString + ", chapter: " + chapter
    }

    val book = new Book()
    println(book set Title to "Scala for the Impatient" set Author to "Cay Horstmann")
  }

  // 4
  {
    class Network {

      class Member(val name: String) {
        val contacts = new scala.collection.mutable.ArrayBuffer[Member]

        override def equals(obj: scala.Any): Boolean = obj match {
          case t: Member => true
          case _ => false
        }
      }

      private val members = new scala.collection.mutable.ArrayBuffer[Member]

      def join(name: String) = {
        val m = new Member(name)
        members += m
      }
    }
  }

  // 5
  {
    import scala.collection.mutable.ArrayBuffer

    class Network {

      class Member(val name: String) {
        val contacts = new ArrayBuffer[Network#Member]

        override def equals(that: Any) = that match {
          case t: Member => true
          case _ => false
        }

        override val toString = this.getClass + ": " + name
      }

      protected val members = new ArrayBuffer[Member]

      def join(name: String): Member = {
        val m = new Member(name)
        members += m
        m
      }
    }

    object Network {
      type NetworkMember = n.Member forSome {val n: Network}

      /**
       * 方法接受相同网络的成员，但拒绝那些来自不同网络的成员
       */
      def process[M <: n.Member forSome {val n : Network}](m1: M, m2: M) = (m1, m2)

      /**
       * 接受相同或不同网络的成员
       * 类型投影: Network#Member意思是 任何Network的Member
       */
      def process2[M <: Network#Member](m1: M, m2: M) = (m1, m2)

      /**
       * 接受相同或不同网络的成员
       */
      def process3(m1: NetworkMember, m2: NetworkMember) = (m1, m2)
    }

    import Network._
    val chatter = new Network
    val myFace = new Network
    val fred = chatter.join("Fred")
    val wilma = chatter.join("Wilma")
    val barney = myFace.join("Barney")

    println(process(fred, wilma))
    //    Error:(184, 5) inferred type arguments [Network#Member] do not conform to method process's type parameter bounds [M <: n.Member forSome { val n: Network }]
    //    process(fred, barney)
    //    ^
    // process(fred, barney) // ERROR

    println(process2(fred, wilma))
    println(process2(fred, barney))

    println(process3(fred, wilma))
    println(process3(fred, barney))
  }

  // 6
  {
    def findIndex(array: Array[Int], n: Int): Either[Int, Int] = {
      // Divide and Conquer
      def findIndexDC(array: Array[Int], start: Int, end: Int, n: Int):
      Either[Int, Int] = {
        if (end - start == 0) {
          if (array(start) == n) Right(array(start)) else Left(array(start))
        }
        else {
          val l = findIndexDC(array, start, start + (end - start) / 2, n)
          val r = findIndexDC(array, start + (end - start) / 2 + 1, end, n)
          //        println("l: " + l)
          //        println("r: " + r)

          if (l.isInstanceOf[Left[_, _]] && r.isInstanceOf[Left[_, _]]) {
            val dl = math.abs(l.left.get - n)
            val dr = math.abs(r.left.get - n)
            if (dl < dr) l else if (dl == dr) l else r
          }
          else if (l.isInstanceOf[Left[_, _]]) {
            r
          }
          else if (r.isInstanceOf[Left[_, _]]) {
            l
          }
          else throw new Error("Cannot, lah!")
        }
      }

      findIndexDC(array, 0, array.size - 1, n)
    }
  }

  // 7
  {
    def process(closeable: {def close(): Unit}): Unit = {
      def doSomething(obj: Any) {
        println("Calling toString on the object: " + obj)
      }

      doSomething(closeable)
      closeable.close()
    }
  }

  // 8
  {
    def printValues(f: {def apply(n: Int): Int}, from: Int, to: Int): Unit = {
      (from to to).foreach((n: Int) => println(f(n)))
    }

    printValues((x: Int) => x * x, 3, 6)
    printValues(Array(1, 1, 2, 3, 4, 8, 13, 21, 34, 55), 3, 6)
  }

  // 9
  {
    abstract class Dim[T](val value: Double, val name: String) {
      protected def create(v: Double): T

      def +(other: Dim[T]) = create(value + other.value)

      override def toString() = value + " " + name
    }

    class Seconds(v: Double) extends Dim[Seconds](v, "s") {
      this: Seconds =>
      override val value = v

      override def create(v: Double) = new Seconds(v)
    }

    class Meters(v: Double) extends Dim[Seconds](v, "m") {
      override def create(v: Double) = new Seconds(v)
    }
  }

  // 10
  {
    class C {
      println("Init C")

      def f {
        println("C.f")
      }
    }

    class D extends C {
      println("Init D")
    }

    trait X1 {
      this: C =>
      println("Init X1")

      override def f {
        println("X1.f")
      }
    }

    trait X2 extends C {
      println("Init X2")

      override def f {
        super.f
        println("X2.f")
      }
    }

    trait X31 extends C with X1 {
      println("Init X31")

      override def f {
        super.f
        println("X31.f")
      }
    }

    trait X32 extends X2 {
      println("Init X32")

      override def f {
        super.f
        println("X32.f")
      }
    }

    (new D with X31).f
    println()
    (new D with X32).f
    println()
  }

}
